'''This module provides the level class and related tile management classes.'''
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division

import copy_reg
import logging

from kharlia.core import NativeClass, CObject
from kharlia.engine.actor import AComponent

from kharlia._engine import (TileFromCoords, TileToX, TileToY, FTileset, FTileData,
                             FTileInfo, FTileLayer, FTileGroup, CLevelBase)

__all__ = [
    'TileFromCoords',
    'TileToX',
    'TileToY',
    'FTileset',
    'FTileData',
    'FTileInfo',
    'FTileLayer',
    'FTileGroup',
    'CLevel'
    ]

GLogger = logging.getLogger('engine')

@NativeClass
class CLevel(CLevelBase, CObject):
    '''
    A game level containing actors and tiles.
    
    Native Members:
        
    MAX_WIDTH:int -- The maximum width of a level in tile groups.
    MAX_HEIGHT:int -- The maximum height of a level in tile groups.
    ActorCount:int -- Number of actors in the level.
    GUID:UUID -- Unique identifier.
    LayerCount:int -- The number of layer info objects in this level.
    LayerIndices:list -- A generated list of layer info indices.
    Name:unicode -- The name of the level.
    Size:FSizeI -- The size of the level in tile groups.
    TileGroupCount:int -- The number of tile groups in the level.
    TileGroupSize:FSizeI -- The size of the level's tile groups in tiles (now constant).
    Tileset:FTileset -- The tileset of the level.
    World:CWorld -- The world containing the level.
    
    Native Methods:
        
    Clear() -- Kill all tasklets, destroy all actors, remove all tile groups.
    GetActorByID(id:int):CActor -- Retrieve an actor using it's level-specific ID.
    GetActorByIndex(index:int):CActor -- Retrieve an actor by its index in the actor list.
    GetActorByTag(tag:unicode):CActor -- Retrieve the first actor with the specified tag.
    
    Members:
        
    ActorTickerTasklet -- The tasklet object used for actor ticking.
    LoadedResources -- A set of resources referenced from the level's unpickling
        operation. Transient.
    '''

    def __init__(self, guid, name, size):
        self.__initnative__(None, guid, name, size)
        CObject.__init__(self)
        self.LoadedResources = None
        #print('created level:', self)
        
    def __del__(self):
        #print('finalizing level:', self)
        CLevelBase.__del__(self)

    def __getstate__(self):
        VERSION = 2
        return (
            VERSION,
            CLevelBase.__getstate__(self)
            )

    def __setstate__(self, state):
        version = state[0]
        CLevelBase.__setstate__(self, state[1])

    def __reduce_ex__(self, protocol):
        assert protocol == 2
        return (_UnpickleLevel, (type(self), self.GUID, self.Name, self.Size), self.__getstate__()) 

    def Clear(self):
        self.LoadedResources = None
        super(CLevel, self).Clear()

    def IterTileGroups(self):
        '''Iterate through tile groups in the level.'''
        for i in xrange(self.TileGroupCount):
            yield self.GetTileGroup(i)

    def IterLayers(self):
        '''Iterate through the level's layer information.

        Returns tuple with layer index and FLevelLayer.
        '''
        for i in xrange(self.LayerCount):
            yield self.GetLayer(i)

    def IterActors(self):
        '''Iterate through all actors in this level.'''
        get = self.GetActorByIndex
        for index in xrange(self.ActorCount):
            yield get(index)

    def IterComponents(self, ctype=None):
        '''Iterate through all components in this level.
        
        This yields a tuple of an actor and one of its components. The component
        is an instance of ctype. If ctype is None, yields all components for all
        actors.
        '''
        if not issubclass(ctype, AComponent):
            raise TypeError('ctype must be subclass of AComponent')
        get = self.GetActorByIndex
        if ctype is None:
            for index in xrange(self.ActorCount):
                actor = get(index)
                cget = actor.GetComponent
                for cindex in xrange(actor.ComponentCount):
                    yield actor, cget(cindex)
        else:
            for index in xrange(self.ActorCount):
                actor = get(index)
                comp = actor.GetComponent(ctype)
                if comp is None:
                    continue
                yield actor, comp
    
    def CreateActor(self, compnames=None, owner=None, name=None, id=0):
        '''Create an actor.

        Arguments:
        compnames -- A component type, global name for a component type, or an iterable
            that yields component types or global names for component types. These will
            be added to the new actor.
        owner -- The optional owner of this new actor.
        name -- Optional, a unicode name for the actor or None. ValueError will
            be raised if this name conflicts with another in the level.
        id -- ID number for new actor, should only be used for network creation.
        '''
        return super(CLevel, self).CreateActor(compnames, owner, name, id)

    def DestroyActor(self, actor):
        '''Destroy the specified actor.'''
        super(CLevel, self).DestroyActor(actor)
    
    #def OnCreated(self):
    #    CLevelBase.OnCreated(self)
    #    
    #def OnDestroyed(self):
    #    CLevelBase.OnDestroyed(self)

def _UnpickleLevel(cls, guid, name, size):
    '''
    Used to create level instance when unpickling
    '''
    assert cls is CLevel
    #newname = CWorld.UnpickleNewName
    #name = newname if newname else name
    return CLevel(guid, name, size)

copy_reg.constructor(_UnpickleLevel)

#def _UpdateLevel(world, resmgr, lpath):
#    # Load target file
#    assert path.isfile(lpath)
#    with io.open(lpath, 'rb') as oldf:
#        lev, resources = CWorld.LoadLevelPackage(oldf, resmgr)
#    # Save to stringIO
#    filestr = cstringio.StringIO()
#    CLevel.SaveLevelPackage(lev, filestr)
#    with io.open(lpath, 'wb') as newf:
#        newf.write(filestr.getvalue())

#def _UpdateLevelDir(world, resmgr, lpath, depth=0, depthlimit=-1):
#    for fbase in os.listdir(lpath):
#        npath = path.join(lpath, fbase)
#        root, ext = path.splitext(fbase)
#        if ext.lower() == '.level':
#            _UpdateLevel(world, resmgr, npath)
#        elif path.isdir(npath) and (depthlimit == -1 or depth < depthlimit):
#            _UpdateLevelDir(world, resmgr, npath, depth + 1, depthlimit)

#def UpdateLevels():
#    '''
#    Commandlet to update level files to latest version.
#    '''
#    logger = logging.getLogger('cmd-updatelevels')
#    logger.info('Starting level update commandlet')
#    app = core.GetApp()

#    # Start the engine service if necessary
#    ensvc = app.ServiceManager.GetServiceNoFail('kharlia.engine.main.CEngineService')
#    if not ensvc.IsStarted:
#        ensvc.Start()
#    eng = ensvc.Engine
#    assert eng

#    # Parse arguments
#    parser = argparse.ArgumentParser(prefix_chars='-+', add_help=False)
#    parser.add_argument('path', type=str, nargs='?', default=os.getcwd())
#    parser.add_argument('+d', '++depth', type=int, nargs=1, default=[0])

#    args, app.Args = parser.parse_known_args(app.Args)

#    # Create a world for the levels
#    world = eng.World = CWorld()
#    assert world.Engine is eng

#    try:
#        if path.isdir(args.path):
#            _UpdateLevelDir(world, eng.ResourceManager, args.path, depthlimit=args.depth[0])
#        else:
#            _UpdateLevel(world, eng.ResourceManager, args.path)
#    finally:
#        eng.World = None